<script setup>
// 引入three
import * as THREE from 'three'
// 引入轨道控制器
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
// 引入gltf模型加载库GLTFLoader.js
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
// 引入gltf解析器（压缩过的模型要使用解析器！！！）
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'
// 引入hdr加载器
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'
// 引入水面
import { Water } from 'three/examples/jsm/objects/Water2'
// 引入补间动画函数
import gsap from 'gsap'


// 初始化三维场景
const scene = new THREE.Scene()


// 初始化相机
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
)
// 设置相机位置
camera.position.set(-3.23, 2.98, 4.06)


// 初始化渲染器
const render = new THREE.WebGLRenderer({
  // 设置抗锯齿
  antialias: true
})
// 设置渲染器大小
render.setSize(window.innerWidth, window.innerHeight)
// 设置色调映射
render.outputEncoding = THREE.sRGBEncoding
render.toneMapping = THREE.ACESFilmicToneMapping
render.toneMappingExposure = 0.5
render.shadowMap.enabled = true
// 启用物理上正确的光照模型
render.physicallyCorrectLights = true
// 将渲染器放置到body页面中
document.body.appendChild(render.domElement)


// 初始化轨道控制器
const controls = new OrbitControls(camera, render.domElement)
controls.target.set(-8, 2, 0)
// 开启阻尼效果
controls.enableDamping = true
// 设置阻尼系数
controls.dampingFactor = 0.05
// 禁用摄像机平移
controls.enablePan = false
// 限制垂直旋转角度
controls.minPolarAngle = 0
controls.maxPolarAngle = Math.PI / 2


// 创建GLTF加载器对象
const gltfLoader = new GLTFLoader()
// 创建解析器对象
const dracoLoader = new DRACOLoader()


// 解析器位置（这个draco解析器的位置在每个three版本的examples/jsm/libs/draco，然后将draco整个文件夹复制粘贴到public）
dracoLoader.setDecoderPath('./draco/')
// 给加载器指定解析器
gltfLoader.setDRACOLoader(dracoLoader)
// 注意！！：默认是在public中找模型，写路径的时候不能再写public
gltfLoader.load('/model/scene.glb', (gltf) => {
  // 返回的场景对象gltf.scene插入到threejs场景中
  const model = gltf.scene
  model.traverse((child) => {
    if (child.name === 'Plane') {
      child.visible = false
    }
    // 判断是否为模型
    if (child.isMesh) {
      child.castShadow = true
      child.receiveShadow = true
    }
  })
  scene.add(model)
})


// 加载hdr贴图
const rgbeloader = new RGBELoader()
rgbeloader.load('./textures/sky.hdr', (envMap) => {
  // 设置球形贴图
  envMap.mapping = THREE.EquirectangularReflectionMapping
  // 设置环境贴图
  scene.background = envMap
  // 设置环境贴图
  scene.environment = envMap
})


// 创建水面
// 创建圆面
const waterGeometry = new THREE.CircleGeometry(300, 32)
const water = new Water(waterGeometry, {
  textureWidth: 512,
  textureHeight: 512,
  color: 0xeeeeff,
  flowDirection: new THREE.Vector2(1, 1),
  scale: 100
})
water.rotation.x = -Math.PI / 2
water.position.y = -0.4
scene.add(water)


// 添加平行光
const ligth = new THREE.DirectionalLight(0xffffff, 1)
ligth.position.set(0, 50, 0)
scene.add(ligth)

// 添加点光源
const pointlight = new THREE.PointLight(0xffffff, 25)
pointlight.position.set(0.5, 2.3, 0)
pointlight.castShadow = true
scene.add(pointlight)

// 创建点光源组
const pointlightGrouip = new THREE.Group()
pointlightGrouip.position.set(-8, 2.5, -1.5)
const radius = 3
const pointlightArr = []
for (let i = 0; i < 3; i++) {
  const sphereGeometry = new THREE.SphereGeometry(0.2, 32)
  const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff })
  const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)

  sphere.position.set(
    radius * Math.cos((i * 2 * Math.PI) / 3),
    Math.cos((i * 2 * Math.PI) / 3),
    radius * Math.sin((i * 2 * Math.PI) / 3)
  )

  const pointLight = new THREE.PointLight(0xffffff, 50)
  sphere.add(pointLight)
  pointlightGrouip.add(sphere)
  pointlightArr.push(sphere)
}
// 将光源组添加到场景中
scene.add(pointlightGrouip)


// 设置画布自适应屏幕大小
window.addEventListener('resize', () => {
  // 重置渲染器输出画布canvas尺寸
  render.setSize(window.innerWidth, window.innerHeight)
  // 全屏情况下：设置观察范围长宽比aspect为窗口宽高比
  camera.aspect = window.innerWidth / window.innerHeight
  // 如果相机的一些属性发生了变化，需要执行updateProjectionMatrix ()方法更新相机的投影矩阵
  camera.updateProjectionMatrix()
})


// 启用补间动画函数
const options = {
  angle: 0
}
gsap.to(options, {
  angle: Math.PI * 2,
  duration: 10,
  repeat: -1,
  ease: 'linear',
  onUpdate: () => {
    pointlightGrouip.rotation.y = options.angle
    pointlightArr.forEach((item, index) => {
      item.position.set(
        radius * Math.cos((index * 2 * Math.PI) / 3),
        Math.cos((index * 2 * Math.PI) / 3 + options.angle * 5),
        radius * Math.sin((index * 2 * Math.PI) / 3)
      )
    })
  }
})


// 使用补间动画移动相机位置
const timeLine1 = gsap.timeline()
const timeLine2 = gsap.timeline()

// 定义相机移动函数
const translateCanera = (position, target) => {
  controls.enabled = false // 禁用控制器
  controls.enableDamping = false // 关闭阻尼效果
  // 要跳转到的位置
  timeLine1.to(camera.position, {
    x: position.x,
    y: position.y,
    z: position.z,
    duration: 1,
    ease: 'power2.inOut'
  }).eventCallback('onComplete', () => {
    controls.enabled = true // 动画完成后再启用控制器
    controls.enableDamping = true // 关闭阻尼效果
  })

  // 跳转时围绕哪个目标旋转
  timeLine2.to(controls.target, {
    x: target.x,
    y: target.y,
    z: target.z,
    duration: 1,
    ease: 'power2.inOut'
  })
}

// 视角一
const shijiao1 = () => {
  translateCanera(new THREE.Vector3(7, 0, 23), new THREE.Vector3(0, 0, 0))
  makeHeart()
}
// 视角二
const shijiao2 = () => {
  translateCanera(new THREE.Vector3(10, 5, 0), new THREE.Vector3(5, 2, 0))
}
// 视角三
const shijiao3 = () => {
  translateCanera(new THREE.Vector3(-20, 1.3, 6.6), new THREE.Vector3(5, 2, 0))
}
// 初始视角
const shijiao4 = () => {
  translateCanera(new THREE.Vector3(-3.23, 2.98, 4.06), new THREE.Vector3(-8, 2, 0))
  restoreHeart()
}

// 创建渲染函数
const animate = () => {
  // 请求动画帧
  requestAnimationFrame(animate)
  // 渲染
  render.render(scene, camera)
  controls.update()
}
// 调用渲染函数
animate()


// 实例化创建漫天星星
let starsInstance = new THREE.InstancedMesh(
  new THREE.SphereGeometry(0.1, 32, 32),
  new THREE.MeshStandardMaterial({
    color: 0xffffff,
    emissive: 0xffffff,
    emissiveIntensity: 10
  }),
  100
)

// 星星随机到天上
let starsArr = []
let endArr = []

for (let i = 0; i < 100; i++) {
  let x = Math.random() * 100 - 50
  let y = Math.random() * 100 - 50
  let z = Math.random() * 100 - 50
  starsArr.push(new THREE.Vector3(x, y, z))

  let matrix = new THREE.Matrix4()
  matrix.setPosition(x, y, z)
  starsInstance.setMatrixAt(i, matrix)
}
scene.add(starsInstance)

// 创建爱心路径
let heartShape = new THREE.Shape()
heartShape.moveTo(25, 25)
heartShape.bezierCurveTo(25, 25, 20, 0, 0, 0)
heartShape.bezierCurveTo(-30, 0, -30, 35, -30, 35)
heartShape.bezierCurveTo(-30, 55, -10, 77, 25, 95)
heartShape.bezierCurveTo(60, 77, 80, 55, 80, 35)
heartShape.bezierCurveTo(80, 35, 80, 0, 50, 0)
heartShape.bezierCurveTo(35, 0, 25, 25, 25, 25)

// 根据爱心路径获取点
let center = new THREE.Vector3(0, 2, 10)
for (let i = 0; i < 100; i++) {
  let point = heartShape.getPoint(i / 100)
  endArr.push(
    new THREE.Vector3(
      point.x * 0.1 + center.x,
      point.y * 0.1 + center.y,
      center.z
    )
  )
}

// 创建爱心动画
function makeHeart() {
  let params = {
    time: 0
  }

  gsap.to(params, {
    time: 1,
    duration: 1,
    onUpdate: () => {
      for (let i = 0; i < 100; i++) {
        let x = starsArr[i].x + (endArr[i].x - starsArr[i].x) * params.time
        let y = starsArr[i].y + (endArr[i].y - starsArr[i].y) * params.time
        let z = starsArr[i].z + (endArr[i].z - starsArr[i].z) * params.time
        let matrix = new THREE.Matrix4()
        matrix.setPosition(x, y, z)
        starsInstance.setMatrixAt(i, matrix)
      }
      starsInstance.instanceMatrix.needsUpdate = true
    }
  })
}

function restoreHeart() {
  let params = {
    time: 0
  }

  gsap.to(params, {
    time: 1,
    duration: 1,
    onUpdate: () => {
      for (let i = 0; i < 100; i++) {
        let x = endArr[i].x + (starsArr[i].x - endArr[i].x) * params.time
        let y = endArr[i].y + (starsArr[i].y - endArr[i].y) * params.time
        let z = endArr[i].z + (starsArr[i].z - endArr[i].z) * params.time
        let matrix = new THREE.Matrix4()
        matrix.setPosition(x, y, z)
        starsInstance.setMatrixAt(i, matrix)
      }
      starsInstance.instanceMatrix.needsUpdate = true
    }
  })
}
</script>

<template>
  <div>
    <div class="BigBox">
      <button @click="shijiao1">视角一</button>
      <button @click="shijiao2">视角二</button>
      <button @click="shijiao3">视角三</button>
      <button @click="shijiao4">初始视角</button>
    </div>
  </div>
</template>

<style lang="scss" scoped>
.BigBox {
  position: fixed;
  left: 10px;
  top: 50px;
  z-index: 9;

  button {
    display: block;
    color: #fff;
    background-color: skyblue;
    font-size: 20px;
    margin-bottom: 20px;
    padding: 5px 10px;
    border: none;
    cursor: pointer;
  }
}
</style>